前篇介紹了將 Uptime Kuma 將監控資訊結合到 Backstage 中,讓我們查看專案的同時也能看到一些服務的運作狀態,像這樣整合資訊便是 Backstage 的核心價值。
如果在內部打造一個技術論壇呢?就像 Github 那樣的 issue 功能,過往開發者遇到的問題可能透過討論解決的紀錄,讓往後的人能夠參考借鑑找出線索,最後也跟專案去整合資訊,能夠即時看到與專案相關的討論議題等等。但是為何不直接使用 Github 上的 issue 功能?論壇中的身份又要如何跟公司開發者連結呢?
以我們的案例,Backstage 整合了我們將不同系統的程式碼放置在不同不平台的分散問題,前面提到的 Github、AzDevops或是 Gitlab 多多少少都有組別採用,也許 Discrouse 真的能夠再次整合這些分散開發問題。再透過登入 Backstage 的 SSO 概念也套用到 Discrouse 之中,很幸運的 Discrouse 本身擁有支援 OIDC 的插件,很不幸的面對 IdentityServer 我必須自行擴充 OIDC PKCE 加密方式。
首先,我們透過簡單的故事來了解 Discrouse 是什麼:
在科技城鎮的另一端,有一家名為 L·Tech 的創新企業。這家公司聚集了來自各地的頂尖開發者,日夜不停地開發著尖端技術。然而,隨著團隊不斷壯大,內部溝通逐漸成為一個巨大的挑戰。不同部門之間的技術分享變得零散,知識的流動不再像以前那麼順暢,這讓許多開發者感到困惑和無助。
就在這個時候,一位名叫莉娜的技術總監決定尋找解決方案。她偶然間發現了一款名為 Discourse 的開源論壇軟體,這款工具號稱是打造企業內部技術論壇的利器。莉娜覺得這或許是解決公司內部溝通問題的關鍵。
她迅速開始研究 Discourse,並發現它的設計簡潔而且功能強大。Discourse 支援多層次的討論結構,讓團隊可以輕鬆地組織各類主題,從技術討論到項目協作,甚至是日常的學習分享。最讓莉娜驚喜的是,Discourse 還支援各種插件和整合,使得它可以完美融入公司的技術生態系統。
莉娜決定將 Discourse 引入到公司內部,為每個技術團隊設立專屬的論壇板塊。開發者們可以隨時隨地在論壇上發表問題、分享經驗,甚至參與頭腦風暴。Discourse 的強大搜尋功能也讓每個人都能輕鬆找到所需的資訊,避免了知識的流失。
隨著時間的推移,L·Tech 的內部技術論壇成為了公司最重要的知識庫。每位成員都能在這裡學習和成長,團隊之間的合作變得更加順暢。莉娜為這個決定感到自豪,因為 Discourse 不僅提升了公司內部的溝通效率,也為公司培育了一個充滿創新精神的技術社群。
從那以後,L·Tech 的技術團隊越來越強大,而這一切都離不開 Discourse 的支持。對於莉娜和她的團隊來說,Discourse 不僅僅是一個工具,更是一個推動公司進步的重要平台。
若將 Discourse 與 Backstage 整合,將專案相關的討論和技術知識直接嵌入到 Backstage 的專案組件頁面中。當團隊成員瀏覽專案時,還可以即時查看與該專案相關的最新討論、常見問題解答、以及技術文檔,這不僅提高了溝通效率,還讓知識共享變得更加自然與高效。Discourse 若成為內部的知識庫,不僅能推動每個專案前進速度,還能提升團隊整體的技術能力與一致性。
Discourse 是一個功能強大的開源論壇系統,但由於其架構比較複雜,包含多個服務組件,如 PostgreSQL(作為數據庫)、Redis(用於緩存)、以及 Sidekiq(處理後台任務),幸好我們可以通過 Docker Compose 來簡化建置的過程,請看以下設定範例 (包含前篇的 uptime_kuma):
services:
nginx:
image: nginx:latest
container_name: nginx
ports:
- "80:80"
- "443:443"
environment:
- NODE_TLS_REJECT_UNAUTHORIZED=0
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
- ./cert/:/etc/nginx/ssl/
networks:
- app-network
uptime_kuma:
image: louislam/uptime-kuma
container_name: kuma
restart: always
ports:
- "3001:3001"
environment:
- UPTIME_KUMA_DISABLE_FRAME_SAMEORIGIN=true
volumes:
- uptime_kuma:/app/data
networks:
- app-network
postgresql:
image: docker.io/bitnami/postgresql:16
container_name: postgresql
volumes:
- 'postgresql_data:/bitnami/postgresql'
environment:
- ALLOW_EMPTY_PASSWORD=yes
- POSTGRESQL_USERNAME=bn_discourse
- POSTGRESQL_DATABASE=bitnami_discourse
networks:
- app-network
redis:
image: docker.io/bitnami/redis:7.0
container_name: redis
environment:
- ALLOW_EMPTY_PASSWORD=yes
volumes:
- 'redis_data:/bitnami/redis'
networks:
- app-network
discourse:
image: docker.io/bitnami/discourse:3
container_name: discourse
volumes:
- 'discourse_data:/bitnami/discourse'
depends_on:
- postgresql
- redis
environment:
- ALLOW_EMPTY_PASSWORD=yes
- DISCOURSE_HOST=discourse.local.com
- DISCOURSE_DATABASE_HOST=postgresql
- DISCOURSE_DATABASE_PORT_NUMBER=5432
- DISCOURSE_DATABASE_USER=bn_discourse
- DISCOURSE_DATABASE_NAME=bitnami_discourse
- DISCOURSE_REDIS_HOST=redis
- DISCOURSE_REDIS_PORT_NUMBER=6379
- POSTGRESQL_CLIENT_POSTGRES_USER=postgres
- POSTGRESQL_CLIENT_CREATE_DATABASE_NAME=bitnami_discourse
- POSTGRESQL_CLIENT_CREATE_DATABASE_EXTENSIONS=hstore,pg_trgm
ports:
- "3080:3000"
networks:
- app-network
sidekiq:
image: docker.io/bitnami/discourse:3
container_name: sidekiq
depends_on:
- discourse
volumes:
- 'sidekiq_data:/bitnami/discourse'
command: /opt/bitnami/scripts/discourse-sidekiq/run.sh
environment:
- ALLOW_EMPTY_PASSWORD=yes
- DISCOURSE_HOST=localhost
- DISCOURSE_DATABASE_HOST=postgresql
- DISCOURSE_DATABASE_PORT_NUMBER=5432
- DISCOURSE_DATABASE_USER=bn_discourse
- DISCOURSE_DATABASE_NAME=bitnami_discourse
- DISCOURSE_REDIS_HOST=redis
- DISCOURSE_REDIS_PORT_NUMBER=6379
networks:
- app-network
networks:
app-network:
volumes:
uptime_kuma: {}
discourse_data: {}
sidekiq_data: {}
postgresql_data: {}
redis_data: {}
因為接下來要串接 SSO,我們一樣幫它設定本地 SSL,以下為新增的 nginx.conf
設定,記得要再去 hosts 設定新網址:
worker_processes auto;
events {
worker_connections 1024;
}
http {
server {
listen 443 ssl;
server_name uptimekuma.local.com;
ssl_certificate /etc/nginx/ssl/localhost.pem;
ssl_certificate_key /etc/nginx/ssl/localhost-key.pem;
location / {
proxy_pass http://uptime_kuma:3001;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
server {
listen 443 ssl;
server_name discourse.local.com;
ssl_certificate /etc/nginx/ssl/localhost.pem;
ssl_certificate_key /etc/nginx/ssl/localhost-key.pem;
location / {
proxy_pass http://discourse:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
}
沒問題的話應該可以看到這個畫面,接下來我們必須先手動建立管理員才能修改設定或是安裝插件等等。
首先進入 discourse 容器中,再執行以下指令就可以創建管理員
// 進入容器指令 (供參考)
sudo docker exec -it discourse bash
// 進入容器中輸入以下指令![https://ithelp.ithome.com.tw/upload/images/20240922/20128232mO5wkhWahN.png](https://ithelp.ithome.com.tw/upload/images/20240922/20128232mO5wkhWahN.png)
cd /opt/bitnami/discourse
RAILS_ENV=production bundle exec rake admin:create
成功後會顯示 Your account now has Admin privileges!
,接著我們就可以用它來登入 Discourse 囉。
在登入介面中可以看到目前只支援原生的帳號密碼登入,還有第三方的 Passkey,我們的目標就是新再新增一個 OIDC IdentityServer 的登入方式。首先我們使用剛剛的管理員帳號密碼登入。
Discourse 的可以變更的設定非常多,可以先到語言選項調整成繁體中文會比較方便,而這個選項也會影響到整個網站的語言顯示。
Discourse 提供了多種內建的登入方式,涵蓋了大部分主流選項。但它不支援直接的第三方 OIDC 登入方式。不過,別擔心,我們可以通過下載 Discourse 的 OIDC 擴充插件來實現這個功能。
Discourse OpenID Connect 插件官方說明 / 圖片來源 - https://meta.discourse.org/t/discourse-openid-connect/103632
安裝方式也非常簡單,一樣要進入到容器內來操作,在插件資料夾直接 clone 插件檔案基本上就安裝完成了。最後可以看到目錄下多了一個 discourse-openid-connect
的資料夾,接著我們需要重啟服務來啟用插件。
cd /opt/bitnami/discourse/plugins
git clone https://github.com/discourse/discourse-openid-connect.git
重新進入 discourse 先到外掛的地方啟用 Openid Connect 插件,再點擊設定。
當為 Discourse 設定 OIDC 之前,請先在 IdentityServer 上創建一個新的客戶端,並使用與當初為 Backstage 設定的客戶端相同的格式。也為 Discourse 也創建一個對應的客戶端,範例如下:
⚠️ 以下將展示在生產環境中成功部署的設定案例,特別點出需要注意的設定。
這邊為 OIDC 必要的設定,其他選項可以依照需求選擇。
當初在這裡這邊卡了很久,最後在官方的討論找到了解決方案。要讓 Discourse 正常使用 OIDC,需要解決跨網域請求、Cookie 政策以及允許讀取其他應用的資料。我們需要允許 Discourse 讀取 IdentityServer 所在的網域與主機位置,並且允許使用 iframe 到 Backstage。
利用在上一篇的 iframe 方式,我們一樣為 Discourse 建立一個前端插件,如法炮製一樣的操作流程,一樣加入在左邊的側邊欄,這邊就不再提一次,直接展示實務上最後呈現的效果。
在 Backstage 中,我們已成功實現透過 OIDC 進行使用者登入,並且瀏覽器會通過 Cookie 來保存使用者的登入狀態。接下來的目標是當使用者點擊 Backstage 中的 Discourse 應用時,能夠自動攜帶使用者的登入資訊,實現無縫整合。理想的使用情境是,使用者只需在首次使用時點擊一次登入按鈕,後續系統會自動將相同的使用者資訊傳遞到 Discourse,無需再次登入。這樣的設計可以大大提升用戶體驗,避免重複認證的麻煩。
PKCE(Proof Key for Code Exchange,代碼交換證明金鑰)是一種提升 OAuth 2.0 和 OpenID Connect 安全性的技術。它通過在授權碼流程中添加額外的驗證步驟,確保授權碼不會被攔截並非法使用。PKCE 是由 Identity Server 預設啟用的功能,旨在進一步加強對授權過程的保護,特別是在公共或不可信的客戶端環境中。
在最後一步登入時還有遇到了一個錯誤,即 Discourse 的 OIDC 插件不支援 PKCE 驗證,這導致 token 傳遞過程中缺少必要的參數,從而導致驗證失敗。我們可以選擇將該插件 clone 下來進行修改,或者直接修改 Discourse 容器中的檔案。
例如我自己 fork 該專案,並通過安裝 Discourse 插件的方式,從自己的 Github 儲存庫抓取修改後的版本來解決這個問題。由於 PKCE 是可選項,並不是每一款身份驗證解決方案都會啟用,以下修改提供參考。https://github.com/discourse/discourse-openid-connect/pull/80
cd /opt/bitnami/discourse/plugins
git clone https://github.com/jincoco88912/pkce_discourse-openid-connect.git
本篇文章主要聚焦於 Backstage 在串接 SSO 後的應用性,較少著墨在 Backstage 本身的細節。這個案例展示了如何利用 SSO 進一步實現 Backstage 集中資訊得的精神。SSO 作為我們的關鍵工具,能夠有效串聯各種平台,實現無縫銜接和整合。
https://github.com/bitnami/containers/blob/main/bitnami/discourse/README.md
https://meta.discourse.org/t/discourse-openid-connect/103632
https://meta.discourse.org/t/openid-connect-plugin-cant-fetch-configuration/253728
https://github.com/discourse/discourse-openid-connect/pull/80